package TreeHole.patches;

import TreeHole.event.TreeHoleEvent;
import TreeHole.mod.ModConfig;
import TreeHole.mod.TreeHoleBase;
import TreeHole.mod.TreeHoleHelper;
import TreeHole.room.TreeHoleEnterRoom;
import TreeHole.save.ExtraSave;
import TreeHole.save.SaveAndContinueFore;
import TreeHole.save.TreeHoleSave;
import basemod.ReflectionHacks;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.evacipated.cardcrawl.modthespire.lib.*;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.core.Settings;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.dungeons.TheEnding;
import com.megacrit.cardcrawl.helpers.TipTracker;
import com.megacrit.cardcrawl.helpers.controller.CInputActionSet;
import com.megacrit.cardcrawl.helpers.input.InputHelper;
import com.megacrit.cardcrawl.map.DungeonMap;
import com.megacrit.cardcrawl.map.MapEdge;
import com.megacrit.cardcrawl.map.MapRoomNode;
import com.megacrit.cardcrawl.random.Random;
import com.megacrit.cardcrawl.rooms.AbstractRoom;
import com.megacrit.cardcrawl.rooms.EmptyRoom;
import com.megacrit.cardcrawl.rooms.MonsterRoomBoss;
import com.megacrit.cardcrawl.saveAndContinue.SaveAndContinue;
import com.megacrit.cardcrawl.saveAndContinue.SaveFile;
import com.megacrit.cardcrawl.screens.DungeonMapScreen;
import com.megacrit.cardcrawl.screens.DungeonTransitionScreen;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

public class TreeHolePatch {
    private static final Logger logger = LogManager.getLogger(TreeHolePatch.class.getName());

    public static boolean hasModAdded = false;

    public static boolean exitTreeHole = false;
    public static boolean enableTreeHole = false;
    public static boolean dontScrollThisTime = false;
    public static SaveFile foreSaveFile;
    public static ExtraSave extraSave = new ExtraSave();

    //树洞相关配置
    public static boolean renderBoss = false;
    public static boolean shouldRespawnTreeHoles = false;
    public static ArrayList<Integer> spawnX;
    public static ArrayList<Integer> spawnY;
    public static ArrayList<Integer> treeHoleTypes;
    public static ArrayList<String> specificIDs;
    public static int currentType;
    //用来判断这个树洞是否已经进入过
    public static ArrayList<Boolean> entered;
    public static ArrayList<String> bossSpawned = new ArrayList<>();
    public static String lastHoleID = "";

    private static void clearSet(){
        exitTreeHole = false;
        enableTreeHole = false;
        dontScrollThisTime = false;
        foreSaveFile = null;
        renderBoss = false;
        shouldRespawnTreeHoles = false;
        spawnX = null;
        spawnY = null;
        specificIDs = null;
        entered = null;
        treeHoleTypes = null;
        currentType = -1;
        bossSpawned.clear();
        lastHoleID = "";
    }

    //保存和加载
    @SpirePatch(clz = SaveAndContinue.class, method = "save")
    public static class SavePatch {
        @SpirePostfixPatch
        public static void Postfix(SaveFile save) {
            if (enableTreeHole && foreSaveFile != null) {
                SaveAndContinueFore.saveFore(save, foreSaveFile);
            }
            else if(CardCrawlGame.chosenCharacter!=null)
                SaveAndContinueFore.deleteSaveFore(CardCrawlGame.chosenCharacter);

            extraSave.onSave();
            SaveAndContinueFore.saveExtra(extraSave);

            //all mods save
            for(TreeHoleBase base:TreeHoleHelper.getAllBases()){
                Class<?> saveClass = base.getSaveClass();
                TreeHoleSave ts = base.saveTreeHoleSave();
                if(ts!=null){
                    ts.onSave();
                    SaveAndContinueFore.saveTreeHoleSave(ts,saveClass);
                }
            }
        }
    }

    @SpirePatch(clz = SaveAndContinue.class, method = "loadSaveFile",paramtypez = {AbstractPlayer.PlayerClass.class})
    public static class LoadPatch {
        @SpirePostfixPatch
        public static SaveFile Postfix(SaveFile _ret, AbstractPlayer.PlayerClass c) {
            if(TreeHoleHelper.contains(_ret.level_name)){
                foreSaveFile = SaveAndContinueFore.loadSaveFore(c);
                if(foreSaveFile!=null){
                    enableTreeHole = true;
                }
            }
            ExtraSave e = SaveAndContinueFore.loadExtra(c);
            if(e!=null){
                extraSave = e;
                extraSave.onLoad();
            }

            //all mods load
            for(TreeHoleBase base:TreeHoleHelper.getAllBases()){
                Class<?> saveClass = base.getSaveClass();
                if(saveClass!=null){
                    TreeHoleSave ts = SaveAndContinueFore.loadTreeHole(c,saveClass);
                    if(ts!=null){
                        ts.onLoad();
                        base.loadTreeHoleSave(ts);
                    }
                }
            }
            return _ret;
        }
    }

    @SpirePatch(clz = SaveAndContinue.class,method = "deleteSave")
    public static class DeletePatch{
        @SpirePostfixPatch
        public static void Postfix(AbstractPlayer p){
            SaveAndContinueFore.deleteSaveFore(p.chosenClass);
            SaveAndContinueFore.deleteExtra(p.chosenClass);

            //all mods delete
            for(TreeHoleBase base:TreeHoleHelper.getAllBases()){
                if(base.getSaveClass()!=null){
                    TreeHoleSave ts = SaveAndContinueFore.loadTreeHole(p.chosenClass,base.getSaveClass());
                    if(ts!=null){
                        ts.onDelete();
                    }
                    SaveAndContinueFore.deleteTreeHole(p.chosenClass,base.getSaveClass());
                }
            }
            clearSet();
        }
    }

    @SpirePatch(clz = CardCrawlGame.class, method = "getDungeon", paramtypez = {String.class, AbstractPlayer.class})
    public static class GetDungeonPatch {
        @SpirePrefixPatch
        public static SpireReturn<AbstractDungeon> Prefix(CardCrawlGame _inst, String key, AbstractPlayer p){
            //不是加载老地图且不是树洞内，需要重新生成地图的树洞们
            if(!TreeHoleHelper.contains(key)&&!exitTreeHole){
                shouldRespawnTreeHoles = true;
            }
            else if(!TreeHoleHelper.contains(key)){
                return SpireReturn.Return(null);
            }
            return SpireReturn.Continue();
        }

        @SpirePostfixPatch
        public static AbstractDungeon Postfix(AbstractDungeon _ret, CardCrawlGame _inst, String key, AbstractPlayer p) {
            if (TreeHoleHelper.contains(key)){
                renderBoss = TreeHoleHelper.isBoss(key,currentType);
                return TreeHoleHelper.getDungeon(key,p,AbstractDungeon.specialOneTimeEventList,currentType);
            }
            else if(exitTreeHole&&foreSaveFile!=null){
                SaveFile file = new SaveFile(SaveFile.SaveType.ENTER_ROOM);
                SaveAndContinueFore.trueCopy(foreSaveFile,file);
                AbstractDungeon.currMapNode.room = new EmptyRoom();
                AbstractDungeon dungeon = _inst.getDungeon(key,p,file);
                logger.info("===当前房间信息："+AbstractDungeon.getCurrRoom().getClass()+" ，"+file.room_x+"-"+file.room_y+"-"+file.current_room);
                //logger.info("===当前BOSS数量："+AbstractDungeon.bossList.size()+" ，BOSS列表："+AbstractDungeon.bossList.toString()+" ===");
                exitTreeHole = false;
                enableTreeHole = false;
                foreSaveFile = null;
                if(!key.equals("Exordium") || Settings.isShowBuild || !(Boolean) TipTracker.tips.get("NEOW_SKIP"))
                    dontScrollThisTime = true;
                return dungeon;
            }
            renderBoss = true;
            return _ret;
        }
    }

    @SpirePatch(clz = CardCrawlGame.class, method = "getDungeon", paramtypez = {String.class, AbstractPlayer.class, SaveFile.class})
    public static class GetDungeonOnSavePatch {
        @SpirePostfixPatch
        public static AbstractDungeon Postfix(AbstractDungeon _ret, CardCrawlGame _inst, String key, AbstractPlayer p, SaveFile file) {
            if (TreeHoleHelper.contains(key)){
                renderBoss = TreeHoleHelper.isBoss(key,currentType);
                return TreeHoleHelper.getDungeon(key,p,file,currentType);
            }
            renderBoss = true;
            return _ret;
        }
    }

    @SpirePatch(clz = DungeonTransitionScreen.class,method = "setAreaName")
    public static class TransitionNamePatch{
        @SpirePostfixPatch
        public static void Postfix(DungeonTransitionScreen _inst, String key){
            if(TreeHoleHelper.contains(key)){
                _inst.levelNum = TreeHoleHelper.getLevelNum(key,currentType);
                _inst.levelName = TreeHoleHelper.getLevelName(key,currentType);
                AbstractDungeon.name = _inst.levelName;
                AbstractDungeon.levelNum = _inst.levelNum;
            }
        }
    }

    //树洞生成（常规地图）
    @SpirePatch(clz = AbstractDungeon.class,method = "generateMap")
    public static class GenerateTreeHolePatch{
        @SpireInsertPatch(rloc = 32)
        public static void Insert(){
            if(!hasModAdded)
                return;
            //生成逻辑，行数要求：7~map.size()-2，不占用第8行，在有连线的结点随机生成3个 /UPDATE:下限~上限 个
            if(shouldRespawnTreeHoles){
                //spawn
                spawnX = new ArrayList<>();
                spawnY = new ArrayList<>();
                specificIDs = new ArrayList<>();
                entered = new ArrayList<>();
                ArrayList<MapRoomNode> nodes = new ArrayList<>();
                for(int i =7;i<AbstractDungeon.map.size()-1;i++){
                    if(i!=8){
                        for(int j =0;j<AbstractDungeon.map.get(i).size();j++){
                            MapRoomNode node = AbstractDungeon.map.get(i).get(j);
                            if(node.hasEdges())
                                nodes.add(node);
                        }
                    }
                }
                //为确保地图生成一致性，使用地图独立随机数
                Random duplicate = AbstractDungeon.mapRng.copy();
                Collections.shuffle(nodes,duplicate.random);
                //随机数2 决定生成数
                int count;
                if(ModConfig.getCountLower()<ModConfig.getCountUpper()){
                    count = duplicate.random(ModConfig.getCountLower(),ModConfig.getCountUpper());
                }
                else
                    count = ModConfig.getCountUpper();
                if(TreeHoleHelper.getSharedIDs().isEmpty()){
                    count = 0;
                }
                int totalCount = count;
                for(String id:TreeHoleHelper.getIDs()){
                    TreeHoleBase base = TreeHoleHelper.getTreeHole(id);
                    for(int i =0;i<base.getSpecificTreeHoleCount();i++){
                        specificIDs.add(id);
                    }
                    totalCount+=base.getSpecificTreeHoleCount();
                }
                for(int i =0;i<totalCount&&i<nodes.size();i++){
                    if(i>=specificIDs.size()){
                        nodes.get(i).room = new TreeHoleEnterRoom();
                    }
                    else {
                        String id = specificIDs.get(i);
                        TreeHoleBase base = TreeHoleHelper.getTreeHole(id);
                        nodes.get(i).room = base.getSpecificTreeHoleEnterRoom();
                    }
                    spawnX.add(nodes.get(i).x);
                    spawnY.add(nodes.get(i).y);
                    entered.add(false);
                }
                shouldRespawnTreeHoles = false;
            }
            else if(spawnX!=null&&spawnY!=null){
                //load
                for(int i =0;i<spawnX.size();i++){
                    if(specificIDs==null || i>=specificIDs.size()){
                        AbstractDungeon.map.get(spawnY.get(i)).get(spawnX.get(i)).room = new TreeHoleEnterRoom();
                    }
                    else {
                        String id = specificIDs.get(i);
                        TreeHoleBase base = TreeHoleHelper.getTreeHole(id);
                        AbstractDungeon.map.get(spawnY.get(i)).get(spawnX.get(i)).room = base.getSpecificTreeHoleEnterRoom();
                    }
                }
            }
        }
    }

    //树洞生成（终幕）
    @SpirePatch(clz = TheEnding.class,method = "generateSpecialMap")
    public static class FinalGeneratePatch{
        @SpirePostfixPatch
        public static void Postfix(TheEnding _inst){
            if(!hasModAdded)
                return;
            if(!TreeHoleHelper.anyEndingEnabled())
                return;
            MapRoomNode shop = AbstractDungeon.map.get(1).get(3);
            MapRoomNode leftFinal = AbstractDungeon.map.get(2).get(1);
            MapRoomNode boss = AbstractDungeon.map.get(3).get(3);
            shop.addEdge(new MapEdge(shop.x,shop.y,shop.offsetX,shop.offsetY,leftFinal.x,leftFinal.y,leftFinal.offsetX,leftFinal.offsetY,false));
            leftFinal.addEdge(new MapEdge(leftFinal.x,leftFinal.y,leftFinal.offsetX,leftFinal.offsetY,boss.x,boss.y,boss.offsetX,boss.offsetY,false));
            leftFinal.room = new TreeHoleEnterRoom();
            if(shouldRespawnTreeHoles){
                spawnX = new ArrayList<>();
                spawnY = new ArrayList<>();
                entered = new ArrayList<>();
                spawnX.add(leftFinal.x);
                spawnY.add(leftFinal.y);
                entered.add(false);
                shouldRespawnTreeHoles = false;
            }
        }
    }

    @SpirePatch(clz = DungeonMap.class,method = "calculateMapSize")
    public static class MapSizePatch{
        @SpirePostfixPatch
        public static float Postfix(float _ret,DungeonMap _inst){
            if(TreeHoleHelper.contains(AbstractDungeon.id))
                return TreeHoleHelper.getMapSize(AbstractDungeon.id,currentType);
            return _ret;
        }
    }

    @SpirePatch(clz = DungeonMap.class,method = "render")
    public static class RenderPatch{
        @SpirePrefixPatch
        public static SpireReturn<Void> Prefix(DungeonMap _inst, SpriteBatch sb){
            if(TreeHoleHelper.contains(AbstractDungeon.id)){
                ReflectionHacks.privateMethod(DungeonMap.class,"renderFinalActMap", SpriteBatch.class).invoke(_inst, sb);
                return SpireReturn.Return();
            }
            return SpireReturn.Continue();
        }
    }

    @SpirePatch(clz = DungeonMap.class,method = "renderMapBlender")
    public static class RenderBlenderPatch{
        @SpirePrefixPatch
        public static SpireReturn<Void> Prefix(DungeonMap _inst, SpriteBatch sb){
            if(TreeHoleHelper.contains(AbstractDungeon.id))
                return SpireReturn.Return();
            return SpireReturn.Continue();
        }
    }

    @SpirePatch(clz = DungeonMapScreen.class,method = "open")
    public static class OpenMapPatch{
        @SpirePrefixPatch
        public static SpireReturn<Void> Prefix(DungeonMapScreen _inst,boolean doScrollingAnimation){
            if(dontScrollThisTime){
                dontScrollThisTime = false;
                return SpireReturn.Return();
            }
            return SpireReturn.Continue();
        }

        @SpireInsertPatch(rloc = 8)
        public static void Insert(DungeonMapScreen _inst,boolean doScrollingAnimation){
            if(TreeHoleHelper.contains(AbstractDungeon.id)){
                ReflectionHacks.setPrivate(_inst,DungeonMapScreen.class,"mapScrollUpperLimit",TreeHoleHelper.getScrollLimit(AbstractDungeon.id,currentType));
            }
        }
    }

    @SpirePatch(clz = DungeonMap.class,method = "renderBossIcon")
    public static class RenderBossPatch{
        @SpirePrefixPatch
        public static SpireReturn<Void> Prefix(DungeonMap _inst, SpriteBatch sb){
            if(enableTreeHole&&!renderBoss){
                return SpireReturn.Return();
            }
            return SpireReturn.Continue();
        }
    }

    @SpirePatch(clz = DungeonMap.class,method = "update")
    public static class UpdatePatch {
        @SpireInsertPatch(rloc = 40)
        public static void Insert(DungeonMap _inst) {
            if (_inst.bossHb.hovered && (InputHelper.justClickedLeft || CInputActionSet.select.isJustPressed())) {
                if (AbstractDungeon.getCurrRoom().phase == AbstractRoom.RoomPhase.COMPLETE && AbstractDungeon.screen == AbstractDungeon.CurrentScreen.MAP) {
                    if (TreeHoleHelper.contains(AbstractDungeon.id) && AbstractDungeon.getCurrMapNode().y == TreeHoleHelper.getBossEnterY(AbstractDungeon.id,currentType) && TreeHoleHelper.isBoss(AbstractDungeon.id,currentType)) {
                        AbstractDungeon.getCurrMapNode().taken = true;
                        MapRoomNode node2 = AbstractDungeon.getCurrMapNode();
                        Iterator var2 = node2.getEdges().iterator();

                        while (var2.hasNext()) {
                            MapEdge e = (MapEdge) var2.next();
                            if (e != null) {
                                e.markAsTaken();
                            }
                        }

                        InputHelper.justClickedLeft = false;
                        CardCrawlGame.music.fadeOutTempBGM();
                        MapRoomNode node = new MapRoomNode(-1, 15);
                        node.room = new MonsterRoomBoss();
                        AbstractDungeon.nextRoom = node;
                        if (AbstractDungeon.pathY.size() > 1) {
                            AbstractDungeon.pathX.add(AbstractDungeon.pathX.get(AbstractDungeon.pathX.size() - 1));
                            AbstractDungeon.pathY.add((Integer) AbstractDungeon.pathY.get(AbstractDungeon.pathY.size() - 1) + 1);
                        } else {
                            AbstractDungeon.pathX.add(1);
                            AbstractDungeon.pathY.add(15);
                        }

                        AbstractDungeon.nextRoomTransitionStart();
                        _inst.bossHb.hovered = false;
                    }
                }
            }
        }
    }

    public static boolean enteredTreeHole(int x,int y){
        for(int i =0;i<entered.size()&&i<spawnX.size()&&i<spawnY.size();i++){
            if(spawnX.get(i)==x&&spawnY.get(i)==y)
                return entered.get(i);
        }
        return false;
    }

    private static void markEntered(int x,int y){
        if(entered == null || spawnX == null || spawnY == null)
            return;
        for(int i =0;i<entered.size()&&i<spawnX.size()&&i<spawnY.size();i++){
            if(spawnX.get(i)==x&&spawnY.get(i)==y){
                entered.set(i,true);
            }
        }
    }

    public static void enterTreeHole(String ID ,int holeType){
        //save
        lastHoleID = ID;
        currentType = holeType;
        enableTreeHole = true;
        markEntered(AbstractDungeon.getCurrMapNode().x,AbstractDungeon.getCurrMapNode().y);
        foreSaveFile = SaveAndContinueFore.makeCopy();
        CardCrawlGame.music.fadeOutBGM();
        CardCrawlGame.music.fadeOutTempBGM();
        AbstractDungeon.fadeOut();
        CardCrawlGame.nextDungeon = ID;
        AbstractDungeon.isDungeonBeaten = true;
        AbstractDungeon.getCurrRoom().phase = AbstractRoom.RoomPhase.COMPLETE;
    }

    public static void outerTreeHole(){
        exitTreeHole = true;
        TreeHoleEnterRoom.justLeaveTreeHole = true;
        CardCrawlGame.music.fadeOutBGM();
        CardCrawlGame.music.fadeOutTempBGM();
        AbstractDungeon.fadeOut();
        AbstractDungeon.id = TreeHolePatch.foreSaveFile.level_name;
        CardCrawlGame.nextDungeon = TreeHolePatch.foreSaveFile.level_name;
        AbstractDungeon.isDungeonBeaten = true;
    }

}
